// SPDX-License-Identifier: GPL-2.0-or-later

/***************************************************************************
 *   Copyright (C) 2024 by opencp inc.                                     *
 *   virteman@opencp.cn                                                    *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <helper/binarybuffer.h>
#include <helper/time_support.h>
#include "helper/types.h"
#include <target/algorithm.h>
#include <target/armv7m.h>
#include <target/cortex_m.h>
#include "imp.h"
#include "../../../contrib/loaders/flash/rm12xx/rm12xx_flash.h"

/* RM12XX flash loader */
static const uint8_t rm12xx_algo[] = {
#include "../../../contrib/loaders/flash/rm12xx/rm12xx_algo.inc"
};

#define HWP_SPIFLASH_ADDR            (0x40300000)
#define RM12XX_FLASH_TIMEOUT_MS 8000
//#define RM12XX_FLASH_TIMEOUT_MS 60000
//#define RM12XX_FLASH_BASE_ADDR 0x64000000

#define FLASH_SIZE_REG_MASK (0xFFFF)

struct rm12xx_flash_bank {
	int ppage_size;
    uint32_t r_rev_id;
	uint32_t sector_length;
	bool probed;
	struct working_area *working_area;
	struct armv7m_algorithm armv7m_info;
	const uint8_t *algo_code;
	uint32_t algo_size;
	uint32_t algo_working_size;
	uint32_t buffer_addr;
	uint32_t params_addr;
};

static int rm12xx_init(struct flash_bank *bank) {
	struct target *target = bank->target;
	struct rm12xx_flash_bank *rm12xx_bank = bank->driver_priv;

	/* Check for working area to use for flash helper algorithm */
	target_free_working_area(target, rm12xx_bank->working_area);
	rm12xx_bank->working_area = NULL;

	int retval = target_alloc_working_area(target, rm12xx_bank->algo_working_size,
				&rm12xx_bank->working_area);
	if (retval != ERROR_OK)
		return retval;

	/* Confirm the defined working address is the area we need to use */
	if (rm12xx_bank->working_area->address != RM12XX_FLASH_LOADER_WORKING_ADDR) {
		LOG_ERROR("%d: Invalid working address", rm12xx_bank->r_rev_id);
		LOG_INFO("Hint: Use '-work-area-phys 0x%" PRIx32 "' in your target configuration",
			RM12XX_FLASH_LOADER_WORKING_ADDR);
		target_free_working_area(target, rm12xx_bank->working_area);
		rm12xx_bank->working_area = NULL;
		return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
	}

	/* Write flash helper algorithm into target memory */
	retval = target_write_buffer(target, RM12XX_FLASH_LOADER_PROGRAM_ADDR,
				rm12xx_bank->algo_size, rm12xx_bank->algo_code);
	if (retval != ERROR_OK) {
		LOG_ERROR("%d: Failed to load flash helper algorithm",
			rm12xx_bank->r_rev_id);
		target_free_working_area(target, rm12xx_bank->working_area);
		rm12xx_bank->working_area = NULL;
		return retval;
	}

	/* Initialize the ARMv7 specific info to run the algorithm */
	rm12xx_bank->armv7m_info.common_magic = ARMV7M_COMMON_MAGIC;
	rm12xx_bank->armv7m_info.core_mode = ARM_MODE_THREAD;

	/* Begin executing the flash helper algorithm */
	retval = target_start_algorithm(target, 0, NULL, 0, NULL,
				RM12XX_FLASH_LOADER_PROGRAM_ADDR, 0,
				&rm12xx_bank->armv7m_info);
	if (retval != ERROR_OK) {
		LOG_ERROR("%d: Failed to start flash helper algorithm",
			rm12xx_bank->r_rev_id);
		target_free_working_area(target, rm12xx_bank->working_area);
		rm12xx_bank->working_area = NULL;
		return retval;
	}

	/*
	 * At this point, the algorithm is running on the target and
	 * ready to receive commands and data to flash the target
	 */

	return retval;
}

static int rm12xx_quit(struct flash_bank *bank) {
	struct target *target = bank->target;
	struct rm12xx_flash_bank *rm12xx_bank = bank->driver_priv;

	/* Regardless of the algo's status, attempt to halt the target */
	(void)target_halt(target);

	/* Now confirm target halted and clean up from flash helper algorithm */
	int retval = target_wait_algorithm(target, 0, NULL, 0, NULL, 0,
					RM12XX_FLASH_TIMEOUT_MS, &rm12xx_bank->armv7m_info);

	target_free_working_area(target, rm12xx_bank->working_area);
	rm12xx_bank->working_area = NULL;

	return retval;
}

static int rm12xx_wait_algo_done(struct flash_bank *bank, uint32_t params_addr) {
	struct target *target = bank->target;
	struct rm12xx_flash_bank *rm12xx_bank = bank->driver_priv;
	uint32_t status_addr = params_addr + offsetof(struct rm12xx_flash_params, sync);
	uint32_t status;
	int64_t start_ms = timeval_ms();

	do {
		int retval = target_read_u32(target, status_addr, &status);
		if (retval != ERROR_OK)
			return retval;

		keep_alive();

		int64_t elapsed_ms = timeval_ms() - start_ms;
		if (elapsed_ms > RM12XX_FLASH_TIMEOUT_MS)
			break;
	} while (status == RM12XX_FLASH_LOADER_EXECUTE);

	if (status != RM12XX_FLASH_LOADER_WAIT) {
		LOG_ERROR("%d: Flash operation failed, status=0x%" PRIx32,
				rm12xx_bank->r_rev_id,
				status);
		return ERROR_FAIL;
	}

	return ERROR_OK;
}


/* flash_bank rm12xx 0 0 0 0 <target#> */
FLASH_BANK_COMMAND_HANDLER(rm12xx_flash_bank_command)
{
	struct rm12xx_flash_bank *rm12xx_info;
	/* Create the bank structure */
	rm12xx_info = calloc(1, sizeof(*rm12xx_info));

	/* Check allocation */
	if (!rm12xx_info) {
		LOG_ERROR("failed to allocate bank structure");
		return ERROR_FAIL;
	}

	//bank->write_start_alignment = 16;
	//bank->write_end_alignment = 16;

	bank->driver_priv = rm12xx_info;

	rm12xx_info->probed = false;

	if (CMD_ARGC < 6)
		return ERROR_COMMAND_SYNTAX_ERROR;

	return ERROR_OK;
}

static int rm12xx_probe(struct flash_bank *bank)
{
	struct target *target = bank->target;
	struct rm12xx_flash_bank *rm12xx_bank = bank->driver_priv;
	uint32_t sector_length = FLASH_SECTOR_SIZE;
	int retval = ERROR_OK;

	/* Set up appropriate flash helper algorithm */
	rm12xx_bank->algo_code = rm12xx_algo;
	rm12xx_bank->algo_size = sizeof(rm12xx_algo);
	rm12xx_bank->algo_working_size = RM12XX_FLASH_LOADER_PARAMS_SIZE +
					RM12XX_FLASH_LOADER_BUFFER_SIZE +
					RM12XX_FLASH_LOADER_PROGRAM_SIZE;
	rm12xx_bank->buffer_addr = RM12XX_FLASH_LOADER_BUFFER_ADDR;
	rm12xx_bank->params_addr = RM12XX_FLASH_LOADER_PARAMS_ADDR;

	retval = target_read_u32(target, HWP_SPIFLASH_ADDR, &rm12xx_bank->r_rev_id);

	if (retval != ERROR_OK)
		return retval;

	//bank->base = FLASH_CODE_BASE;
	bank->base = FLASH_UNCACHED_BASE;
	bank->size = FLASH_SIZE;
	bank->num_sectors = bank->size/FLASH_SECTOR_SIZE;
	bank->sectors = realloc(bank->sectors, sizeof(struct flash_sector) * bank->num_sectors);
	rm12xx_bank->sector_length = sector_length;

	for (unsigned int i = 0; i < bank->num_sectors; i++) {
		bank->sectors[i].offset = i * FLASH_SECTOR_SIZE;
		bank->sectors[i].size = FLASH_SECTOR_SIZE;
		bank->sectors[i].is_erased = -1;
		bank->sectors[i].is_protected = 0;
	}

	rm12xx_bank->probed = true;

	return ERROR_OK;
}

static int rm12xx_auto_probe(struct flash_bank *bank)
{
	struct rm12xx_flash_bank *rm12xx_bank = bank->driver_priv;

	if (rm12xx_bank->probed)
		return ERROR_OK;

	return rm12xx_probe(bank);
}

static int rm12xx_chip_erase(struct flash_bank *bank)
{
	struct target *target = bank->target;
	struct rm12xx_flash_bank *rm12xx_bank = bank->driver_priv;
	struct rm12xx_flash_params algo_params;

	if (target->state != TARGET_HALTED) {
		LOG_ERROR("Target not halted");
		return ERROR_TARGET_NOT_HALTED;
	}

	/* Make sure we've probed the flash to get the device and size */
	int retval = rm12xx_auto_probe(bank);
	if (retval != ERROR_OK)
		return retval;

	retval = rm12xx_init(bank);
	if (retval != ERROR_OK)
		return retval;

	/* Set up algorithm parameters for chip erase command */
	target_buffer_set_u32(target, (uint8_t *)&algo_params.cmd, RM12XX_FLASH_CMD_ERASE_ALL);
	target_buffer_set_u32(target, (uint8_t *)&algo_params.sync, RM12XX_FLASH_LOADER_WAIT);

	/* Set algorithm parameters */
	retval = target_write_buffer(target, rm12xx_bank->params_addr,
				sizeof(algo_params), (uint8_t *)&algo_params);
	if (retval != ERROR_OK) {
		(void)rm12xx_quit(bank);
		return retval;
	}

	/* Issue flash helper algorithm parameters for chip erase */
	target_buffer_set_u32(target, (uint8_t *)&algo_params.sync, RM12XX_FLASH_LOADER_EXECUTE);
	retval = target_write_buffer(target, rm12xx_bank->params_addr,
				sizeof(algo_params), (uint8_t *)&algo_params);


	/* If no error, wait for chip erase finish */
	if (retval == ERROR_OK)
		retval = rm12xx_wait_algo_done(bank, rm12xx_bank->params_addr);

	/* Regardless of errors, try to close down algo */
	(void)rm12xx_quit(bank);

	return retval;
}

static int rm12xx_erase(struct flash_bank *bank, unsigned int first,
		unsigned int last)
{
	struct target *target = bank->target;
	struct rm12xx_flash_bank *rm12xx_bank = bank->driver_priv;
	struct rm12xx_flash_params algo_params;

	if (target->state != TARGET_HALTED) {
		LOG_ERROR("Target not halted");
		return ERROR_TARGET_NOT_HALTED;
	}

	if ((first == 0) && (last == (bank->num_sectors - 1))) {
		/* Request chip erase */
		return rm12xx_chip_erase(bank);
	}

	uint32_t address = bank->base + first * rm12xx_bank->sector_length;
	uint32_t length = (last - first + 1) * rm12xx_bank->sector_length;

	/* Make sure we've probed the flash to get the device and size */
	int retval = rm12xx_auto_probe(bank);
	if (retval != ERROR_OK)
		return retval;

	retval = rm12xx_init(bank);
	if (retval != ERROR_OK)
		return retval;

	/* Set up algorithm parameters for erase command */
	target_buffer_set_u32(target, (uint8_t *)&algo_params.addr, address);
	target_buffer_set_u32(target, (uint8_t *)&algo_params.len, length);
	target_buffer_set_u32(target, (uint8_t *)&algo_params.cmd, RM12XX_FLASH_CMD_ERASE_SECTORS);
	target_buffer_set_u32(target, (uint8_t *)&algo_params.sync, RM12XX_FLASH_LOADER_WAIT);

	/* Set algorithm parameters */
	retval = target_write_buffer(target, rm12xx_bank->params_addr,
				sizeof(algo_params), (uint8_t *)&algo_params);
	if (retval != ERROR_OK) {
		(void)rm12xx_quit(bank);
		return retval;
	}

	/* Issue flash helper algorithm parameters for erase */
	target_buffer_set_u32(target, (uint8_t *)&algo_params.sync, RM12XX_FLASH_LOADER_EXECUTE);
	retval = target_write_buffer(target, rm12xx_bank->params_addr,
				sizeof(algo_params), (uint8_t *)&algo_params);

	/* If no error, wait for erase to finish */
	if (retval == ERROR_OK)
		retval = rm12xx_wait_algo_done(bank, rm12xx_bank->params_addr);

	/* Regardless of errors, try to close down algo */
    //为了调试，给注释了
	(void)rm12xx_quit(bank);

	return retval;
}

static int rm12xx_write(struct flash_bank *bank, const uint8_t *buffer,
	uint32_t offset, uint32_t count)
{
	struct target *target = bank->target;
	struct rm12xx_flash_bank *rm12xx_bank = bank->driver_priv;
	struct rm12xx_flash_params algo_params;

	if (target->state != TARGET_HALTED) {
		LOG_ERROR("Target not halted");
		return ERROR_TARGET_NOT_HALTED;
	}

	/* Make sure we've probed the flash to get the device and size */
	int retval = rm12xx_auto_probe(bank);
	if (retval != ERROR_OK)
		return retval;

	retval = rm12xx_init(bank);
	if (retval != ERROR_OK)
		return retval;

	/* Initialize algorithm parameters to default values */
	target_buffer_set_u32(target, (uint8_t *)&algo_params.cmd, RM12XX_FLASH_CMD_PROGRAM);

	uint32_t address = bank->base + offset;

	while (count > 0) {
		uint32_t size = (count > RM12XX_FLASH_LOADER_BUFFER_SIZE) ?
							RM12XX_FLASH_LOADER_BUFFER_SIZE : count;

		/* Put the data into buffer */
		retval = target_write_buffer(target, rm12xx_bank->buffer_addr,
					size, buffer);
		if (retval != ERROR_OK) {
			LOG_ERROR("Unable to write data to target memory");
			break;
		}

		/* Update algo parameters for flash write */
		target_buffer_set_u32(target, (uint8_t *)&algo_params.addr, address);
		target_buffer_set_u32(target, (uint8_t *)&algo_params.len, size);
		target_buffer_set_u32(target, (uint8_t *)&algo_params.sync, RM12XX_FLASH_LOADER_WAIT);

		/* Set algorithm parameters */
		retval = target_write_buffer(target, rm12xx_bank->params_addr,
				sizeof(algo_params), (uint8_t *)&algo_params);
		if (retval != ERROR_OK)
			break;

		/* Issue flash helper algorithm parameters for flash write */
		target_buffer_set_u32(target, (uint8_t *)&algo_params.sync, RM12XX_FLASH_LOADER_EXECUTE);
		retval = target_write_buffer(target, rm12xx_bank->params_addr,
				sizeof(algo_params), (uint8_t *)&algo_params);
		if (retval != ERROR_OK)
			break;

        //临时返回, 用于调试
        //return retval;

		/* Wait for flash write finish */
		retval = rm12xx_wait_algo_done(bank, rm12xx_bank->params_addr);
		if (retval != ERROR_OK)
			break;

		count -= size;
		buffer += size;
		address += size;
	}

	/* Regardless of errors, try to close down algo */
	(void)rm12xx_quit(bank);

	return retval;
}


/* This method must return a string displaying information about the bank */
static int rm12xx_get_info(struct flash_bank *bank, struct command_invocation *cmd)
{
	struct rm12xx_flash_bank *rm12xx_info = bank->driver_priv;

	if (!rm12xx_info->probed) {
		int retval = rm12xx_probe(bank);
		if (retval != ERROR_OK) {
			command_print_sameline(cmd, "Unable to find bank information.");
			return retval;
		}
	}

	command_print_sameline(cmd, "flash r_rev_id: %x\n", rm12xx_info->r_rev_id);
	return ERROR_OK;
}

const struct flash_driver rm12xx_flash = {
	.name = "rm12xx",
	.flash_bank_command = rm12xx_flash_bank_command,
	.erase = rm12xx_erase,
	.protect = NULL,
	.write = rm12xx_write,
	.read = default_flash_read,
	.probe = rm12xx_probe,
	.erase_check = default_flash_blank_check,
	.protect_check = NULL,
	.auto_probe = rm12xx_auto_probe,
	.info = rm12xx_get_info,
	.free_driver_priv = default_flash_free_driver_priv,
};
